package com.aleyn.processor.scanner

import com.aleyn.annotation.Factory
import com.aleyn.annotation.Singleton
import com.aleyn.processor.data.Definition
import com.aleyn.processor.data.DefinitionAnnotation
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType

/**
 * @author : Aleyn
 * @date : 2023/07/27 : 21:33
 *
 * 负责扫描并处理被依赖注入注解（如 `@Singleton`, `@Factory`）标记的类。
 *
 * 该扫描器的核心职责是将 KSP 在编译期发现的、带有 DI 注解的类符号 (`KSClassDeclaration`)
 * 转换成 LRouter 内部统一的元数据表现形式 `Definition.ClassDefinition`。
 * 这个元数据对象详细描述了一个类如何被实例化和管理（例如，它的作用域、构造函数依赖、
 * 是否懒加载、绑定的接口等），是后续代码生成步骤的基础。
 */
class ClassInjectScanner(private val logger: KSPLogger) {

    /**
     * 为一个被 DI 注解标记的类，创建一个完整的依赖注入定义。
     *
     * @param element 被注解的 KSP 元素，这里预期是一个 `KSClassDeclaration`。
     * @return [Definition.ClassDefinition] 一个包含了该类所有注入信息的元数据对象。
     *         如果一个类被多个 DI 注解标记，该方法会选择第一个有效的注解来创建定义。
     */
    fun createClassDefinition(element: KSAnnotated): Definition {
        val ksClassDeclaration = (element as KSClassDeclaration)
        val packageName = ksClassDeclaration.packageName.asString()
        val className = ksClassDeclaration.simpleName.asString()
        val qualifier = ksClassDeclaration.getQualifier() // 获取类上可能存在的限定符
        val annotations = element.annotations.filterValidAnn() // 过滤出有效的 DI 注解 (Singleton, Factory)

        // 遍历所有有效注解，并使用第一个成功匹配的注解来创建注入定义
        return annotations.firstNotNullOf {
            createClassDefinition(
                it,
                ksClassDeclaration,
                packageName,
                qualifier,
                className,
            )
        }

    }

    /**
     * 根据一个具体的 DI 注解和类信息，创建详细的 `ClassDefinition`。
     *
     * @param annotation 当前正在处理的注解实例 (`@Singleton` 或 `@Factory`)。
     * @param declaration 目标类的 KSP 声明。
     * @param packageName 目标类的包名。
     * @param qualifier 从类上解析出的限定符 (qualifier)。
     * @param className 目标类的简单名称。
     * @return [Definition.ClassDefinition] 一个包含了所有细节的注入定义元数据。
     */
    private fun createClassDefinition(
        annotation: KSAnnotation,
        declaration: KSClassDeclaration,
        packageName: String,
        qualifier: String?,
        className: String,
    ): Definition.ClassDefinition {

        // 默认情况下，将定义绑定到该类的第一个父接口或父类。这允许通过接口来获取实例。
        val binding = declaration.superTypes.firstOrNull()?.resolve()?.declaration

        // 解析主构造函数的参数，以确定该类的构造函数依赖关系
        val constructorParams = declaration.primaryConstructor
            ?.parameters?.map(::getConstructorParameter)

        val keyword: DefinitionAnnotation
        var lazy: Boolean? = null

        var declarationBind: KSType?
        when (annotation.shortName.asString()) {
            Singleton::class.simpleName -> {
                keyword = SINGLETON
                // 从 `@Singleton` 注解的参数中获取 `lazy` 的值，如果未指定，默认为 true
                lazy = annotation.arguments.getArgValue("lazy") ?: true
                // 从 `@Singleton` 注解的参数中获取 `bind` 的类型，用于显式指定绑定
                declarationBind = annotation.arguments.getArgValue<KSType>("bind")
            }

            Factory::class.simpleName -> {
                keyword = FACTORY
                // 从 `@Factory` 注解的参数中获取 `bind` 的类型
                declarationBind = annotation.arguments.getArgValue<KSType>("bind")
            }

            else -> {
                // 如果遇到未知的注解类型，记录错误并抛出异常
                logger.error("Unknown annotation type: ${annotation.shortName.asString()}\"")
                error("Unknown annotation type: ${annotation.shortName.asString()}")
            }
        }

        // 确定最终的绑定类型。如果注解中通过 `bind` 参数显式指定了类型，则优先使用它。
        var currentBind = binding
        declarationBind?.declaration?.let {
            // 确保 `bind` 参数不是默认的 `Unit` 类型
            if (it.qualifiedName?.asString() != "kotlin.Unit") {
                currentBind = it
            }
        }
        // 创建并返回最终的 ClassDefinition 对象
        return Definition.ClassDefinition(
            packageName,
            qualifier,
            lazy,
            keyword,
            className,
            constructorParams ?: emptyList(),
            currentBind,
        )
    }
}